#include "base_cpp/scanner.h"
#include "base_cpp/output.h"
#include "molecule/molfile_loader.h"
#include "reaction/rxnfile_loader.h"
//#include "dingo/render_reaction.h"
//#include "dingo/render_molecule.h"
#include "cairo/include/cairo/cairo.h"
#include "base_c/os_dir.h"
#include "base_c/os_thread.h"

#ifdef _WIN32
#include <windows.h>
#undef min
#undef max
   void open (const char* filename)
   {
      ShellExecuteA(NULL, "open", filename, NULL, NULL, SW_SHOWMAXIMIZED);
   }
#else
   void open (const char* filename)
   {
      
   }
#endif

#include "dingo/c/dingo_c.h"
#define DCK(op) if (op == 0) {printf("%s", dingoGetLastError()); scanf("*");}
#define DCK1(op) if (op == 0) {printf("%s", dingoGetLastError());}
#define DCK2(op, id) if (op == 0) {printf("%i: %s", id, dingoGetLastError());}
void TestBatchMolfiles ()
{
   dingoBeginLog("dingo.log");

   OsDirIter iter;
   int rc;

   if (osDirSearch("../data/molfiles", "*.mol", &iter) != OS_DIR_OK)
      return;

   while ((rc = osDirNext(&iter)) == OS_DIR_OK)
   {
      Array<char> infilename;
      infilename.appendString(iter.path, true);

      Array<char> outfilename;
      outfilename.appendString(infilename.ptr(), true);
      outfilename.resize(outfilename.size()-5);
      outfilename.appendString(".png", true);

      DCK(dingoLoadMolFromFile(infilename.ptr()));
      DCK(dingoSetMarginFactor(0.6f));
      DCK(dingoSetOutputFormat("png"));
      DCK(dingoSetOutputFile(outfilename.ptr()));
      DCK(dingoRender());
   }

   dingoEndLog();
}

void TestBatchReactions ()
{
   dingoBeginLog("dingo.log");

   OsDirIter iter;
   int rc;

   if (osDirSearch("../data/rxn", "*.rxn", &iter) != OS_DIR_OK)
      return;

   while ((rc = osDirNext(&iter)) == OS_DIR_OK)
   {
      Array<char> infilename;
      infilename.appendString(iter.path, true);

      Array<char> outfilename;
      outfilename.appendString(infilename.ptr(), true);
      outfilename.resize(outfilename.size()-5);
      outfilename.appendString(".png", true);

      DCK(dingoLoadReactionFromFile(infilename.ptr()));
      DCK(dingoSetMarginFactor(0.55f));
      DCK(dingoSetOutputFormat("png"));
      DCK(dingoSetOutputFile(outfilename.ptr()));
      DCK(dingoRender());
   }

   dingoEndLog();
}

void TestStrOpt ()
{
   DCK(dingoSetOptions("adasdasd=0932429834,3242342,23-42;BGR =17023;dFfsF= 1.0,1,1."));
   DCK(dingoSetOptions("kngb  =  \t aaaaa"));
   DCK(dingoSetOptions("sdc=;"));
   scanf("*");
}

void Test1 ()
{
   const char* testname = "test.png";
   DCK(dingoSetOutputFormat("png"));
   DCK(dingoLoadMolFromString("O=C(N(CS)CC)[C@@H]1CN([C@@H]2CC3=CNC4=CC=CC(=C34)C2=C1)Cl"));
   DCK(dingoRender());
   byte* bb = NULL;
   int len = 0;
   DCK(dingoGetResult(&bb, &len));
   FileOutput fo(testname);
   fo.write(bb, len);
   open(testname);
}

void TestConsole ()
{
   const char* fmt = "pdf";
   char testname[1024];
   sprintf(testname, "test.%s", strcmp(fmt, "wmf") ? fmt : "emf");

   dingoBeginLog("dingo.log");
   Array<char> opts;
   ArrayOutput output(opts);
   output.printf("query=true; output-format=%s; output-File=%s", fmt, testname);
   output.writeChar(0);
   DCK(dingoSetOptions(opts.ptr()));
   DCK(dingoSetOptionColor("background-color", 1.0f, 1.0f, 1.0f));
   //DCK(dingoSetOption("aromatization", "arom"));

   //dingoSetCenterDoubleBondWhenStereoAdjacent(1);
   //DCK(dingoSetColoring(1));
   //DCK(dingoSetHighlightThicknessEnabled(1));
   //DCK(dingoLoadMolFromFile("../data/molfiles/complex_DT.mol"));
   //DCK(dingoLoadMolFromFile("../data/molfiles/complex_arom.mol"));
   //dingoSetImageSize(2000, 2000);
   //dingoSetAromatization("aromatize");
   //dingoSetLabelMode("hideterminal");
   //dingoSetImplicitHydrogenMode("all");
   
   //DCK(dingoLoadMolFromFile("../../molecule/tests/molfiles/pseudo1_v2000.mol"));

   //DCK(dingoLoadMolFromFile("../data/molfiles/double-stereo.mol"));
   //C1C=CC=CC=1>>C1C=CC=CC=1
   DCK(dingoLoadReactionFromString("SCN(CC)C(=O)[C@H]1CN([C@@H]2CC3=CNC4=CC=CC(=C34)C2=C1)Cl>>CCCCC"));
   DCK(dingoSetComment("SCN(CC)C(=O)[C@H]1CN([C@@H]2CC3=CNC4=CC=CC(=C34)C2=C1)Cl>>CCCCC"));
   DCK(dingoSetCommentAlignment("center"));
   DCK(dingoSetCommentPosition("top"));
   DCK(dingoSetCommentFontSize(20));
   DCK(dingoSetOption("comment-color", "0.5,0.0,0.5"));

   //DCK(dingoLoadReactionFromString("C1=CC=CC=C1>>C#C.C1CCCC1"));   
   //DCK(dingoLoadReactionFromString("C=C>H-C(=O)OO.C=C>CC"));   
   //DCK(dingoLoadReactionFromString("C=C>C=C>CC"));   
   //DCK(dingoLoadMolFromString("C1C=CC=CC=1N"));
   //DCK(dingoSetOptions("output-format=mol; output-File=layout.mol; query=1"));
   //DCK(dingoLoadMolFromString("O=C(N(CS)CC)[C@@H]1CN([C@@H]2CC3=CNC4=CC=CC(=C34)C2=C1)Cl"));
   //DCK(dingoSetOutputFormat("mol"));
   //DCK(dingoLoadMolFromFile("../data/molfiles/rich.mol"));
   //DCK(dingoLoadReactionFromFile("../data/rxn/cat.smiles"));
   
   //DCK(dingoLayout());
   DCK(dingoRender());

   dingoEndLog();

   open(testname);
}

#include "molecule/molecule_auto_loader.h"

TL_DECL(Molecule, mol);
void TestReuse ()
{
   for (int i = 0; i < 1000; ++i)
   {
      printf("%i\n", i);
      TL_SET_SESSION_ID(i % 5);

      TL_GET(Molecule, mol);
      FileScanner scanner("../data/molfiles/complex_DT.mol");
      MolfileLoader loader(scanner);
      loader.loadMolecule(mol, true);
   }
}

void TestFontBug ()
{
   cairo_surface_t *surface;
   cairo_t *cr;
   surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 600, 600);
   cr = cairo_create(surface);
   cairo_set_source_rgb(cr, 0, 0, 0);   
   cairo_select_font_face (cr, "Arial", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);

   const char* txt = "N";
   cairo_set_font_size(cr, 10);
   cairo_text_extents_t ext;
   cairo_text_extents(cr, txt, &ext);
   for (int j = 0; j < 50; ++j)
      for (int i = 0; i < 50; ++i)
      {
         cairo_move_to(cr, (ext.width * (1.999 + j * 0.0005)) * i + 20, j * 12 + 11);
         cairo_show_text(cr, txt);
      }

   cairo_destroy(cr);
   cairo_surface_write_to_png(surface, "test.png");
   cairo_surface_destroy(surface);

   open("test.png");
}

#define DCK(op) if (op == 0) {printf("%s", dingoGetLastError()); scanf("*");}
void testErgo ()
{
   byte* tmpBuf;
   int output_size;
   int error = 0;
   for (int i = 0; i < 100; ++i)
   {
		DCK(dingoLoadMolFromString("CCN(CC)C(=O)[C@H]1CN([C@@H]2CC3=CNC4=CC=CC(=C34)C2=C1)C"));
      DCK(dingoSetOutputFormat("png"))
      //DCK(dingoSetImageSize(800, 800));
      DCK(dingoSetBondLength(333));
      DCK(dingoSetColoring(1));
      DCK(dingoSetStereoOldStyle(1));
      DCK(dingoSetHighlightColorEnabled(0));
      DCK(dingoSetHighlightThicknessEnabled(0));   
      DCK(dingoRender());
      DCK(dingoGetResult(&tmpBuf, &output_size));
      printf("done %i\n", i);
   }
   scanf("*");
}

#ifdef _WIN32
#include <cairo-win32.h>
void TestWin32 ()
{
   cairo_surface_t *surface;
   cairo_t *cr;
   surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 600, 600);
   cr = cairo_create(surface);
   cairo_set_source_rgb(cr, 1, 1, 1);
   cairo_paint(cr);

   LOGFONTW lf;
   lf.lfHeight = 1;
   lf.lfWidth = 0;
   lf.lfEscapement = 0;
   lf.lfOrientation = 0;
   lf.lfWeight = FW_NORMAL;
   lf.lfItalic = false;
   lf.lfUnderline = false;
   lf.lfStrikeOut = false;
   lf.lfCharSet = DEFAULT_CHARSET;
   lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
   lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
   lf.lfQuality = CLEARTYPE_QUALITY;
   memcpy(lf.lfFaceName, L"Times New Roman", sizeof(L"Times New Roman"));

   cairo_font_face_t *ff = cairo_win32_font_face_create_for_logfontw(&lf);
   cairo_matrix_t font_scale, ctm;
   cairo_matrix_init_identity(&ctm);
   cairo_matrix_init_identity(&font_scale);
   cairo_matrix_scale(&font_scale, 14, 14);
   cairo_font_options_t *fopt = cairo_font_options_create();
   cairo_scaled_font_t*sf = cairo_scaled_font_create(ff, &font_scale, &ctm, fopt);
   cairo_set_source_rgb(cr, 0, 0, 0);

   const char* txt = "0";
   cairo_set_scaled_font(cr, sf);
   cairo_text_extents_t ext;
   cairo_text_extents(cr, txt, &ext);
   for (int j = 0; j < 50; ++j)
      for (int i = 0; i < 50; ++i)
      {
         cairo_move_to(cr, (ext.width * (1.999 + j * 0.0005)) * i + 20, j * (1.5f * ext.height) + 11);
         cairo_show_text(cr, txt);         
      }

   cairo_destroy(cr);
   cairo_surface_write_to_png(surface, "test1.png");
   cairo_surface_destroy(surface);

   open("test1.png");

   scanf("*");
}


void TestWin32Emf (bool metafile)
{
   cairo_surface_t *surface;
   cairo_t *cr;
   if (metafile) 
   {
      surface = cairo_win32_printing_surface_create (CreateEnhMetaFile(NULL, "test.emf", NULL, "Test\0\0"));
      StartPage(cairo_win32_surface_get_dc(surface));
   }
   else
      surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 600, 600);
   cr = cairo_create(surface);

   LOGFONTW lf;
   lf.lfHeight = lf.lfWidth = 1;
   lf.lfEscapement = lf.lfOrientation = 0;
   lf.lfWeight = FW_NORMAL;
   lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = false;
   lf.lfCharSet = DEFAULT_CHARSET;
   lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
   lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
   lf.lfQuality = CLEARTYPE_QUALITY;
   memcpy(lf.lfFaceName, L"Arial", sizeof(L"Arial"));

   cairo_font_face_t *ff = cairo_win32_font_face_create_for_logfontw(&lf);
   cairo_matrix_t font_scale, ctm;
   cairo_matrix_init_identity(&ctm);
   cairo_matrix_init_identity(&font_scale);
   cairo_matrix_scale(&font_scale, 14, 14);
   cairo_font_options_t *fopt = cairo_font_options_create();
   cairo_scaled_font_t*sf = cairo_scaled_font_create(ff, &font_scale, &ctm, fopt);
   cairo_set_source_rgb(cr, 0, 0, 0);

   const char* txt = "0";
   cairo_set_scaled_font(cr, sf);
   cairo_text_extents_t ext;
   cairo_text_extents(cr, txt, &ext);
   for (int j = 0; j < 30; ++j)
   {
      for (int i = 0; i < 30; ++i)
      {
         cairo_move_to(cr, ext.width * 2 * i, j * 2 * ext.height);
         cairo_show_text(cr, txt);         
      }
   } 

   cairo_surface_flush(surface);
      
   if (metafile) 
   {
      cairo_surface_show_page(surface);
      EndPage(cairo_win32_surface_get_dc(surface));
   }
   cairo_destroy(cr);
   if (!metafile) 
      cairo_surface_write_to_png(surface, "test.png");
   HDC hdc = cairo_win32_surface_get_dc(surface);
   cairo_surface_destroy(surface);
   if (metafile) 
   {
      DeleteEnhMetaFile(CloseEnhMetaFile(hdc));
   }

   if (metafile) 
      open("test.emf");
   else                
      open("test.png");
}
#endif
void TestSID ()
{
   TL_SET_SESSION_ID(2);
   TL_SET_SESSION_ID(1);
   TL_SET_SESSION_ID(2);
   printf("%lld\n", TL_ALLOC_SESSION_ID()); // 0
   printf("%lld\n", TL_ALLOC_SESSION_ID()); // 3
   printf("%lld\n", TL_ALLOC_SESSION_ID()); // 4
   TL_RELEASE_SESSION_ID(2);
   TL_RELEASE_SESSION_ID(1);
   TL_RELEASE_SESSION_ID(4);
   printf("%lld\n", TL_ALLOC_SESSION_ID()); // 4
   printf("%lld\n", TL_ALLOC_SESSION_ID()); // 1
   printf("%lld\n", TL_ALLOC_SESSION_ID()); // 2
   TL_SET_SESSION_ID(1);
   TL_SET_SESSION_ID(1);
   TL_RELEASE_SESSION_ID(1);
   printf("%lld\n", TL_ALLOC_SESSION_ID()); // 1
   printf("%lld\n", TL_ALLOC_SESSION_ID()); // 5
   printf("%lld\n", TL_ALLOC_SESSION_ID()); // 6
   printf("%lld\n", TL_ALLOC_SESSION_ID()); // 7
   scanf("*");
}

static int threadId;
static os_mutex g_mutex;
static RedBlackStringMap<int> g_map;

static unsigned long hash (byte* bb, int length) {
   unsigned long h = 0;
   for (int i = 0; i < length; ++i) {
      h = h * 31lu + (unsigned long)bb[i];
   }
   return h;
}

THREAD_RET THREAD_MOD run(void *param) {
   osMutexLock(&g_mutex);
   int id = (int)threadId++;
   osMutexUnlock(&g_mutex);
   char buf[1024];
   qword sid = dingoGetSessionId();
   printf("%i: %lld\n", id, sid);
   int j = 0;
   DCK2(dingoSetOutputFormat("png"), id);
   DCK2(dingoSetBondLength(40), id);
   sprintf(buf, "png/%i.png", id);
   DCK2(dingoSetOutputFile(buf), id);
   DCK2(dingoLoadMolFromString("CC(=O)C(N)C(=O)C(N)S"), id);
   DCK2(dingoRender(), id);
   DCK2(dingoReset(), id);
   return 0;
}

TL_DECL(Array<char>, arr);

THREAD_RET THREAD_MOD run1(void *param) {
   const char *str = "Hello World!";
   TL_GET(Array<char>, arr);
   arr.clear();
   arr.appendString(str, true);
   return 0;
}

struct FixedBuf {
   FixedBuf (int sz) : pos(0), size(sz), buf((byte*)malloc(size)) {}
   ~FixedBuf () { free(buf); }
   int pos;
   int size;
   byte *buf;
};

cairo_status_t writer (void *closure, const unsigned char *data, unsigned int length)
{
   FixedBuf &buf = *(FixedBuf*)closure;
   if (buf.pos + (int)length >= buf.size)
      return CAIRO_STATUS_WRITE_ERROR;
   for (int i = 0; i < (int)length; ++i)
      buf.buf[buf.pos++] = (byte)data[i];
   return CAIRO_STATUS_SUCCESS;
}

#define NLETTERS 10
double fontSize[NLETTERS][NLETTERS];
void initFontSizes () {
   for (int j = 0; j < NLETTERS; ++j)
      for (int i = 0; i < NLETTERS; ++i)
         fontSize[j][i] = floorf(10 * (20 + (10.0f * rand() / RAND_MAX)) / 10);
}

THREAD_RET THREAD_MOD runPure(void *param) {
   osMutexLock(&g_mutex);
   int id = (int)threadId++;
   osMutexUnlock(&g_mutex);
   char buf[1024];
   const char* symbols[] = {"a", "b", "c", "d", "e", "f", "g", "h"};
   int sz = 600;
   FixedBuf fb(16000);
   for (int i = 0; i < 50; ++i) {
      cairo_surface_t *surface;
      cairo_t *cr;
      surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 400, 400);
      cr = cairo_create(surface);

      cairo_set_source_rgb(cr, 0, 0, 0);

      cairo_select_font_face(cr, "Arial", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
      for (int j = 0; j < NLETTERS; ++j) {
         for (int i = 0; i < NLETTERS; ++i) {
            cairo_move_to(cr, (i+1) * sz / (NLETTERS + 1), (j+1) * sz / (NLETTERS + 1));
            cairo_set_font_size(cr, fontSize[j][i]);
            osMutexLock(&g_mutex);
            cairo_show_text(cr, symbols[(i + j) % NELEM(symbols)]);
            osMutexUnlock(&g_mutex);
         }
      }

      cairo_surface_write_to_png_stream(surface, writer, &fb);
      int len = fb.pos;
      int h = hash(fb.buf, len);
      printf("%i: %i\n", id, len);
      sprintf(buf, "png/%i_%lu.png", len, h);
      osMutexLock(&g_mutex);
      if (!g_map.find(buf)) {
         g_map.insert(buf, 1);
         FileOutput fo(buf);
         fo.write(fb.buf, len);
         fo.flush();
      }             
      osMutexUnlock(&g_mutex);
      fb.pos = 0;
   }
   return 0;
}

#define NUM 32

void TestConcurrent () {
   //initFontSizes();
   osMutexCreate(&g_mutex);
   for (int i = 0; i < NUM; ++i) {
      osThreadCreate(run, NULL);
   }
}

void TestConcurrent1 () {
   for (int i = 0; i < NUM; ++i)
      osThreadCreate(run1, NULL);
}

int main (void)
{

   try
   { 
      //TestConcurrent();
      //Test1();
      //TestStrOpt();
      //TestSID();
      //TestPango();
      //TestFT();
      //printf("%s", dingoVersionRevisionString());
      //testErgo();
      //TestWin32();
      TestConsole();
      //TestWin32Emf(false);
      //TestWin32Emf(true);
      //TestBatchMolfiles();
      //TestBatchReactions();
      //TestFontBug();
      //TestReuse();
      //scanf("*");
   }
   catch (Exception &e)
   {
      printf("%s\n", e.message());
      scanf("*");
   }
   scanf("*");
   return 0;
}
